package fons.model.query;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import fons.model.ontology.Context;
import fons.model.ontology.AbstractContextDependent;
import fons.model.ontology.Dimension;
import fons.model.ontology.Filter;
import fons.model.ontology.Filters;
import fons.model.ontology.Relation;
import fons.model.ontology.Role;
import fons.model.ontology.Value;
import fons.model.ontology.language.DimensionSentenceElement;
import fons.model.ontology.language.Sentence;
import fons.model.ontology.language.SentenceElement;
import fons.model.query.fun.Function;
import fons.model.query.fun.ListFun;
import fons.model.query.fun.Number;

public class Predicate extends AbstractContextDependent {
	private final Relation relation;
	private final Map<Role, DimensionSentenceElement> dimensionsByRole = Maps.newHashMap();
	private final Set<Role> implicitRoles = Sets.newHashSet();
	
	
	private final List<SentenceElement> removals = Lists.newLinkedList();
	private final List<SentenceElement> additions = Lists.newLinkedList();
	
	@SuppressWarnings("unused")
	private void removeLater(SentenceElement element) {
		removals.add(element);
	}
	
	private void addLater(SentenceElement element) {
		additions.add(element);
	}
	private void cleanup (Sentence sentence, SentenceElement position) {
		for (SentenceElement element: removals) {
			sentence.remove(element);
		}
		
		for (SentenceElement addition: additions) {
			sentence.insertAfter(position, addition);
		}
	}
	
	
	protected Predicate(Context context) {
		super(context);
		relation = null;
	}
	
	public Predicate(Relation relation) {
		super(relation.getContext());
		this.relation = relation;
	}

	public boolean applyOn(Sentence sentence, SentenceElement position) {
		this.addRolesFromSentence(sentence);
		this.addImplicitRoles();
		if (dimensionsByRole.keySet().equals(relation.getRoles())) {
			cleanup(sentence, position);
			return true;
		}
		return false;
	}

	private void addRolesFromSentence(Sentence sentence) {
		for (DimensionSentenceElement dimensionElement: sentence.getElementsOf(DimensionSentenceElement.class)) {
			Set<Dimension> dimensions = dimensionElement.getDimensions();
			final Role role = relation.getRoleOfAll(dimensions);
			
			System.out.println("Rolle von " + dimensionElement.getText() + " ist: " + role);
			
			if (role != null) {
				if (dimensionsByRole.containsKey(role)) {
					//DimensionSentenceElement existing = dimensionsByRole.get(role);
					
					dimensionsByRole.put(role, dimensionElement);//get(role).addAll(dimensionElement.getDimensions());
				} else {
					dimensionsByRole.put(role, dimensionElement);//Sets.newHashSet(dimensionElement.getDimensions()));
				}
			}
		}
	}

	private void addImplicitRoles() {
		for (Role role: relation.getRoles()) {
			if (!dimensionsByRole.containsKey(role)) {
				if (role.isImplicit()) {
					DimensionSentenceElement implicitElement = new DimensionSentenceElement(role.getImplicitDimensions());
					dimensionsByRole.put(role, implicitElement);//Sets.newHashSet(role.getImplicitDimensions()));
					addLater(implicitElement);
					implicitRoles.add(role);
				}
			}
		}
	}

	public Relation getRelation() {
		return this.relation;
	}
	
	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("Predicate \"" + relation.getName() + "\":\n\t");
		for (Role role: dimensionsByRole.keySet()) {
			if (implicitRoles.contains(role)) {
				builder.append(role.getName() + " is implicitly defined as " + dimensionsByRole.get(role).getDimensions());
			} else {
				builder.append(role.getName() + " is given as " + dimensionsByRole.get(role).getDimensions());
			}
			builder.append("\n\t");
		}
		return builder.toString();
	}
	

	public Function getNumbers() {
		Filter filter = null;
		for (DimensionSentenceElement element: dimensionsByRole.values()) {
			Set<Dimension> dimensions = element.getDimensions();
			if (filter == null) {
				filter = Filters.anyOf(dimensions);
				System.out.println("anyOf " + dimensions);
			} else {
				filter = filter.and(Filters.anyOf(dimensions));
				System.out.println("and anyOf " + dimensions);
			}
		}
				
		Set<Value> values = filter.filter(getContext().getValues());
		Function fun = new ListFun();
		for (Value value: values) {
			fun = fun.apply(Number.from(value, dimensionsByRole.keySet()));
		}
		return fun;
	}

	public Collection<? extends Role> getExplicitRoles() {
		return Sets.difference(dimensionsByRole.keySet(), implicitRoles);
	}

	public Set<Role> getImplicitRoles() {
		return Collections.unmodifiableSet(implicitRoles);
	}
}
